package metadata

import (
	"github.com/pingcap/errors"
	"github.com/romberli/das/global"
	"github.com/romberli/das/internal/dependency/metadata"
	"github.com/romberli/go-util/constant"
	"github.com/romberli/go-util/middleware"
	"github.com/romberli/log"
)

var _ metadata.ResourceRoleRepo = (*ResourceRoleRepo)(nil)

// ResourceRoleRepo implements dependency.ResourceRoleRepo interface
type ResourceRoleRepo struct {
	Database middleware.Pool
}

// NewResourceRoleRepo returns metadata.ResourceRoleRepo with given middleware.Pool
func NewResourceRoleRepo(db middleware.Pool) metadata.ResourceRoleRepo {
	return newResourceRoleRepo(db)
}

// NewResourceRoleRepoWithGlobal returns metadata.ResourceRoleRepo with global mysql pool
func NewResourceRoleRepoWithGlobal() metadata.ResourceRoleRepo {
	return newResourceRoleRepo(global.DASMySQLPool)
}

// newResourceRoleRepo returns *ResourceRoleRepo with given middleware.Pool
func newResourceRoleRepo(db middleware.Pool) *ResourceRoleRepo {
	return &ResourceRoleRepo{db}
}

// Execute executes given command and placeholders on the middleware
// it executes command with arguments on database
func (rrr *ResourceRoleRepo) Execute(command string, args ...interface{}) (middleware.Result, error) {
	conn, err := rrr.Database.Get()
	if err != nil {
		return nil, err
	}
	defer func() {
		err = conn.Close()
		if err != nil {
			log.Errorf("metadata ResourceRoleRepo.Execute(): close database connection failed.\n%+v", err)
		}
	}()

	return conn.Execute(command, args...)
}

// Transaction returns a mysql.Transaction that could execute multiple commands as a transaction
func (rrr *ResourceRoleRepo) Transaction() (middleware.Transaction, error) {
	return rrr.Database.Transaction()
}

// GetAll gets all resource groups from the middleware
func (rrr *ResourceRoleRepo) GetAll() ([]metadata.ResourceRole, error) {
	sql := `
		SELECT id, role_uuid, role_name, del_flag, create_time, last_update_time
		FROM t_meta_resource_role_info
		WHERE del_flag = 0
		ORDER BY id ;
	`
	log.Debugf("metadata ResourceRoleRepo.GetAll() sql:\n%s", sql)

	result, err := rrr.Execute(sql)
	if err != nil {
		return nil, err
	}
	// init []metadata.ResourceRole
	resourceRoleList := make([]metadata.ResourceRole, result.RowNumber())
	for i := range resourceRoleList {
		resourceRoleList[i] = NewEmptyResourceRoleInfoWithGlobal()
	}
	// map to struct
	err = result.MapToStructSlice(resourceRoleList, constant.DefaultMiddlewareTag)
	if err != nil {
		return nil, err
	}

	return resourceRoleList, nil
}

// GetByID gets the resource role by the identity from the middleware
func (rrr *ResourceRoleRepo) GetByID(id int) (metadata.ResourceRole, error) {
	sql := `
		SELECT id, role_uuid, role_name, del_flag, create_time, last_update_time
		FROM t_meta_resource_role_info
		WHERE del_flag = 0
		  AND id = ? ;
	`
	log.Debugf("metadata ResourceRoleRepo.GetByID() sql:\n%s\nplaceholders: %d", sql, id)

	result, err := rrr.Execute(sql, id)
	if err != nil {
		return nil, err
	}
	switch result.RowNumber() {
	case 0:
		return nil, errors.Errorf("metadata ResourceRoleRepo.GetByID(): data does not exist, id: %d", id)
	case 1:
		resourceRoleInfo := NewEmptyResourceRoleInfoWithGlobal()
		// map to struct
		err = result.MapToStructByRowIndex(resourceRoleInfo, constant.ZeroInt, constant.DefaultMiddlewareTag)
		if err != nil {
			return nil, err
		}

		return resourceRoleInfo, nil
	default:
		return nil, errors.Errorf("metadata ResourceRoleRepo.GetByID(): duplicate key exists, id: %d", id)
	}
}

// GetID gets the identity with given resource role id from the middleware
func (rrr *ResourceRoleRepo) GetID(roleUUID string) (int, error) {
	sql := `SELECT id FROM t_meta_resource_role_info WHERE del_flag = 0 AND role_uuid = ?;`
	log.Debugf("metadata ResourceRoleRepo.GetID() select sql:\n%s\nplaceholders: %s", sql, roleUUID)
	result, err := rrr.Execute(sql, roleUUID)
	if err != nil {
		return constant.ZeroInt, err
	}

	return result.GetInt(constant.ZeroInt, constant.ZeroInt)
}

// GetByRoleUUID gets the resource role with given resource role id from the middleware
func (rrr *ResourceRoleRepo) GetByRoleUUID(roleUUID string) (metadata.ResourceRole, error) {
	sql := `
		SELECT id, role_uuid, role_name, del_flag, create_time, last_update_time
		FROM t_meta_resource_role_info
		WHERE del_flag = 0
		  AND role_uuid = ?;
	`
	log.Debugf("metadata ResourceRoleRepo.GetByName() select sql:\n%s\nplaceholders: %s", sql, roleUUID)
	result, err := rrr.Execute(sql, roleUUID)
	if err != nil {
		return nil, err
	}
	switch result.RowNumber() {
	case 0:
		return nil, errors.Errorf("metadata ResourceRoleRepo.GetByName(): data does not exist, roleUUID: %s", roleUUID)
	case 1:
		resourceRoleInfo := NewEmptyResourceRoleInfoWithGlobal()
		// map to struct
		err = result.MapToStructByRowIndex(resourceRoleInfo, constant.ZeroInt, constant.DefaultMiddlewareTag)
		if err != nil {
			return nil, err
		}

		return resourceRoleInfo, nil
	default:
		return nil, errors.Errorf("metadata ResourceRoleRepo.GetByName(): duplicate key exists, roleUUID: %s", roleUUID)
	}
}

// GetResourceGroupsByID gets the resource group which this role belongs to with given resource role id from the middleware
func (rrr *ResourceRoleRepo) GetResourceGroupsByID(id int) ([]metadata.ResourceGroup, error) {
	sql := `
		SELECT DISTINCT rgi.id,
						rgi.group_uuid,
						rgi.group_name,
						rgi.del_flag,
						rgi.create_time,
						rgi.last_update_time
		FROM t_meta_resource_group_info rgi
			INNER JOIN t_meta_resource_group_resource_role_map rrm ON rgi.id = rrm.resource_group_id
			INNER JOIN t_meta_resource_role_info rri ON rri.id = rrm.resource_role_id
		WHERE rgi.del_flag = 0
		  AND rrm.del_flag = 0
		  AND rri.del_flag = 0
		  AND rri.id = ? ;
	`

	log.Debugf("metadata ResourceRoleRepo.GetResourceGroup() select sql:\n%s\nplaceholders: %d", sql, id)
	result, err := rrr.Execute(sql, id)
	if err != nil {
		return nil, err
	}

	resourceGroupList := make([]metadata.ResourceGroup, result.RowNumber())
	for row := range resourceGroupList {
		resourceGroupList[row] = NewEmptyResourceGroupInfoWithGlobal()
	}
	// map to struct
	err = result.MapToStructSlice(resourceGroupList, constant.DefaultMiddlewareTag)
	if err != nil {
		return nil, err
	}

	return resourceGroupList, nil
}

// GetUsersByID gets the users of the given id from the middleware
func (rrr *ResourceRoleRepo) GetUsersByID(id int) ([]metadata.User, error) {
	sql := `
		SELECT DISTINCT ui.id,
						ui.user_name,
						ui.department_name,
						ui.employee_id,
						ui.account_name,
						ui.email,
						ui.telephone,
						ui.mobile,
						ui.role,
						ui.del_flag,
						ui.create_time,
						ui.last_update_time
		FROM t_meta_user_info ui
			INNER JOIN t_meta_resource_role_user_map rrum ON ui.id = rrum.user_id
			INNER JOIN t_meta_resource_role_info rri ON rrum.resource_role_id = rri.id
		WHERE ui.del_flag = 0
		  AND rrum.del_flag = 0
		  AND rri.del_flag = 0
		  AND rri.id = ? ;
	`
	log.Debugf("metadata ResourceRoleRepo.GetUsersByID() sql:\n%s\nplaceholders: %d", sql, id)

	result, err := rrr.Execute(sql, id)
	if err != nil {
		return nil, err
	}

	userList := make([]metadata.User, result.RowNumber())
	for row := range userList {
		userList[row] = NewEmptyUserInfoWithGlobal()
	}
	// map to struct
	err = result.MapToStructSlice(userList, constant.DefaultMiddlewareTag)
	if err != nil {
		return nil, err
	}

	return userList, nil
}

// Create creates data with given entity in the middleware
func (rrr *ResourceRoleRepo) Create(resourceRole metadata.ResourceRole) (metadata.ResourceRole, error) {
	sql := `
		INSERT INTO t_meta_resource_role_info(role_uuid, role_name)
		VALUES(?,?);`
	log.Debugf("metadata ResourceRoleRepo.Create() insert sql:\n%s\nplaceholders: %s, %s", sql, resourceRole.GetRoleUUID(), resourceRole.GetRoleName())
	// execute
	_, err := rrr.Execute(sql,
		resourceRole.GetRoleUUID(),
		resourceRole.GetRoleName())
	if err != nil {
		return nil, err
	}
	// get role id
	id, err := rrr.GetID(resourceRole.GetRoleUUID())
	if err != nil {
		return nil, err
	}
	// get entity
	return rrr.GetByID(id)
}

// Update updates data with given entity in the middleware
func (rrr *ResourceRoleRepo) Update(entity metadata.ResourceRole) error {
	sql := `
		UPDATE t_meta_resource_role_info SET role_uuid = ?, role_name = ?, del_flag = ?
		WHERE id = ?;`
	log.Debugf("metadata ResourceRoleRepo.Update() update sql:\n%s\nplaceholders: %s, %s, %d, %d", sql, entity.GetRoleUUID(), entity.GetRoleName(), entity.GetDelFlag(), entity.Identity())
	_, err := rrr.Execute(
		sql,
		entity.GetRoleUUID(),
		entity.GetRoleName(),
		entity.GetDelFlag(),
		entity.Identity(),
	)

	return err
}

// Delete deletes data in the middleware, it is recommended to use soft deletion,
// therefore use update instead of delete
func (rrr *ResourceRoleRepo) Delete(id int) error {
	tx, err := rrr.Transaction()
	if err != nil {
		return err
	}
	defer func() {
		err = tx.Close()
		if err != nil {
			log.Errorf("metadata ResourceRoleRepo.Delete(): close database connection failed.\n%+v", err)
		}
	}()

	err = tx.Begin()
	if err != nil {
		return err
	}
	sql := `DELETE FROM t_meta_resource_role_info WHERE id = ?;`
	log.Debugf("metadata ResourceRoleRepo.Delete() delete sql(t_meta_resource_role_info):\n%s\nplaceholders: %d", sql, id)
	_, err = rrr.Execute(sql, id)
	if err != nil {
		return err
	}

	sql = `DELETE FROM t_meta_resource_group_resource_role_map WHERE resource_role_id = ?;`
	log.Debugf("metadata ResourceRoleRepo.Delete() delete sql(t_meta_resource_group_resource_role_map):\n%s\nplaceholders: %d", sql, id)
	_, err = rrr.Execute(sql, id)
	if err != nil {
		return err
	}

	sql = `DELETE FROM t_meta_resource_role_user_map WHERE resource_role_id = ?;`
	log.Debugf("metadata ResourceRoleRepo.Delete() delete sql(t_meta_resource_role_user_map):\n%s\nplaceholders: %d", sql, id)
	_, err = rrr.Execute(sql, id)
	if err != nil {
		return err
	}

	return tx.Commit()
}

// AddResourceGroup adds a map of resource role and resource group in the middleware
func (rrr *ResourceRoleRepo) AddResourceGroup(resourceRoleID int, resourceGroupID int) error {
	resourceGroupRepo := NewResourceGroupRepoWithGlobal()
	_, err := resourceGroupRepo.GetByID(resourceGroupID)
	if err != nil {
		return err
	}
	_, err = rrr.GetByID(resourceRoleID)
	if err != nil {
		return err
	}
	sql := `INSERT INTO t_meta_resource_group_resource_role_map(resource_group_id, resource_role_id) VALUES(?, ?);`
	log.Debugf("metadata ResourceRoleRepo.AddResourceGroup() insert sql:\n%s\nplaceholders: %d, %d", sql, resourceGroupID, resourceRoleID)
	_, err = rrr.Execute(sql, resourceGroupID, resourceRoleID)

	return err
}

// DeleteResourceGroup deletes the map of resource role and resource group from the middleware
func (rrr *ResourceRoleRepo) DeleteResourceGroup(resourceRoleID int, resourceGroupID int) error {
	resourceGroupRepo := NewResourceGroupRepoWithGlobal()
	_, err := resourceGroupRepo.GetByID(resourceGroupID)
	if err != nil {
		return err
	}
	_, err = rrr.GetByID(resourceRoleID)
	if err != nil {
		return err
	}
	sql := `DELETE FROM t_meta_resource_group_resource_role_map WHERE resource_group_id = ? AND resource_role_id = ?;`
	log.Debugf("metadata ResourceRoleRepo.AddResourceGroup() insert sql:\n%s\nplaceholders: %d, %d", sql, resourceGroupID, resourceRoleID)
	_, err = rrr.Execute(sql, resourceGroupID, resourceRoleID)

	return err
}

// AddUser adds a map of the resource role and user from the middleware
func (rrr *ResourceRoleRepo) AddUser(resourceRoleID int, userID int) error {
	userRepo := NewUserRepoWithGlobal()

	_, err := userRepo.GetByID(userID)
	if err != nil {
		return err
	}
	_, err = rrr.GetByID(resourceRoleID)
	if err != nil {
		return err
	}
	sql := `INSERT INTO t_meta_resource_role_user_map(resource_role_id, user_id) VALUES(?, ?);`
	log.Debugf("metadata ResourceRoleRepo.AddUser() insert sql:\n%s\nplaceholders: %d, %d", sql, resourceRoleID, userID)
	_, err = rrr.Execute(sql, resourceRoleID, userID)

	return err
}

// DeleteUser deletes the map of the resource role and user from the middleware
func (rrr *ResourceRoleRepo) DeleteUser(resourceRoleID int, userID int) error {
	userRepo := NewUserRepoWithGlobal()
	_, err := userRepo.GetByID(userID)
	if err != nil {
		return err
	}
	_, err = rrr.GetByID(resourceRoleID)
	if err != nil {
		return err
	}
	sql := `DELETE FROM t_meta_resource_role_user_map WHERE resource_role_id = ? AND user_id = ?;`
	log.Debugf("metadata AppRepo.DeleteUser() delete sql:\n%s\nplaceholders: %d, %d", sql, resourceRoleID, userID)
	_, err = rrr.Execute(sql, resourceRoleID, userID)

	return err
}
